/**
 * Systemprogrammierung
 * Multiplayer-Quiz
 *
 * Client
 *	\author
 *	\file client/listener.c:
 *	\brief Implementierung des Listener-Threads */

#include "common/sockets.h"
#include "gui/gui_interface.h"
#include "listener.h"
#include "fragewechsel.h"
#include "command.h"
#include "main.h"

int sock; /**<  Globale Variable für Server Socket */

int client_id; /**< Globale ClientId */

int status = 0; /**< Globaler Spielstatus */

/**	\brief Spielstatus holen */
int get_game_status() {
	return status;
}

/**	\brief Spielstatus setzen
 * \param game Parameter zum Überprüfen ob das spiel gerade läuft */
void set_game_status(int game) {
	if (game == GAME_RUNNING) {
		status = GAME_RUNNING;
	}
	return;
}

/** \brief Player Struktur */
typedef struct player_data {
	char name[32]; /**< Spielername */
	uint32_t score; /**< Punktestand */
	uint8_t id; /**< SpielerId	*/
} PLAYER_DATA;

PLAYER_DATA players[MAX_USERS]; /**< Spielerarray */

/** \brief Speichere neue Player_list
 * 	\param server_msg Beinhaltet Server Nachricht */
void set_player_data(struct RFC_MSG *server_msg) {

	int i, offset = 0;

	for (i = 0; i < MAX_USERS; i++) {

		if (offset < server_msg->header.length) {

			memset(players[i].name, 0, 32);

			memcpy(players[i].name, server_msg->data + offset, 32);

			memcpy(&players[i].score, server_msg->data + offset + 32, 4);

			players[i].score = ntohl(players[i].score);

			memcpy(&players[i].id, server_msg->data + offset + 36, 1);

			offset = offset + 37;

		} else {

			strcpy(players[i].name, "");

			players[i].score = 0;

			players[i].id = 255;
		}
	}
}

/** \brief Aktualisiere Preparation-GUI mit neuer Player_list */
int prep_player_list() {

	preparation_clearPlayers();

	int i;
	for (i = 0; i < MAX_USERS; i++) {

		if (players[i].id != 255) {

			debugPrint("%s", players[i].name);

			char name[36] = { 0 };

			strcpy(name, players[i].name);

			if (players[i].id == 0) {

				strcat(name, "(SL)");
			}

			preparation_addPlayer(name);
		}
	}

	return 0;
}

/** \brief Aktualisiere Game-GUI mit neuer Player_list */
int game_player_list() {

	int position, offset = { 0 };

	for (position = 1; position <= MAX_USERS; position++) {

		if (players[position - 1].id == 255) {

			game_setPlayerName(position, NULL);

		} else {

			char name[36] = { 0 };

			strcpy(name, players[position - 1].name);

			if (players[position - 1].id == 0) {

				strcat(name, "(SL)");
			}

			game_setPlayerName(position, name);

			game_setPlayerScore(position, players[position - 1].score);

			if (players[position - 1].id == get_client_id()) {

				game_highlightPlayer(position);
			}
		}

		offset = offset + 37;

	}
	return 0;
}

/** \brief Hebe gewählten Katalog hervor
 * 	\param server_msg Beinhaltet Server Nachricht */
int highlight_catalog(struct RFC_MSG *server_msg) {

	// Kopiere Daten des Void-Pointer
	char* chosen_catalog = (char*) malloc(server_msg->header.length + 1);

	memset(chosen_catalog, 0, server_msg->header.length + 1);

	memcpy(chosen_catalog, server_msg->data, server_msg->header.length);

	// Hebe Katalog hervor
	if (preparation_selectCatalog(chosen_catalog) == 0) {

		debugPrint("Katalog nicht in Liste gefunden\n");

		return -1;
	}
	return 0;
}

/** \brief Zeige Kataloge an
 *	\param server_msg Beinhaltet Server Nachricht */
int add_catalog(struct RFC_MSG *server_msg) {

	// Kopiere Daten des Void-Pointer

	if (server_msg->header.length != 0) {

		char* catalog = (char*) malloc(server_msg->header.length + 1);

		memcpy(catalog, server_msg->data, server_msg->header.length);

		memset(catalog + server_msg->header.length, '\0', 1);

		// fügt Katalog hinzu
		preparation_addCatalog(catalog);
	}

	return 0;
}

/** \brief Starte Spiel
 *	\param server_msg Beinhaltet Server Nachricht */
int start_game(struct RFC_MSG *server_msg) {

	char* start_msg = { "Katalog wird geladen..." };

	if (server_msg->header.length > 0) {

		char* kat = (char*) malloc(server_msg->header.length + 1);
		memset(kat, 0, server_msg->header.length + 1);
		memcpy(kat, server_msg->data, server_msg->header.length);

		char* start_txt = { "Katalog  wird geladen..." };

		start_msg = (char*) malloc(strlen(kat) + strlen(start_txt) + 1);

		memset(start_msg, 0, strlen(kat) + strlen(start_txt) + 1);

		memcpy(start_msg, start_txt, 8);

		memcpy(start_msg + 8, kat, strlen(kat));

		memcpy(start_msg + 8 + strlen(kat), start_txt + 8, strlen(start_txt)
				- 8);
	}

	preparation_hideWindow();

	game_setAnswerButtonsEnabled(0);

	game_setQuestion("3... 2... 1...");

	game_showWindow();

	game_setStatusText(start_msg);

	return 0;
}

/** \brief Zeige neue Frage
 *	\param server_msg Beinhaltet Server Nachricht */
int new_question(struct RFC_MSG *server_msg) {

	game_setAnswerButtonsEnabled(1);

	game_unmarkAnswers();

	QuestionMessage question;

	int offset = 0;

	memcpy(question.question, server_msg->data, QUESTION_SIZE);

	offset = offset + QUESTION_SIZE;

	int i;
	for (i = 0; i < 4; i++) {

		memcpy(&question.answers[i][0], server_msg->data + offset, ANSWER_SIZE);

		offset = offset + ANSWER_SIZE;
	}

	memcpy(&question.timeout, server_msg->data + offset,
			sizeof(question.timeout));

	game_setQuestion(question.question);

	for (i = 0; i < 4; i++) {

		game_setAnswer(i, &question.answers[i][0]);
	}

	question.timeout = ntohs(question.timeout);

	char timeout[12];

	sprintf(timeout, "Zeit: %us", question.timeout);

	game_setStatusIcon(STATUS_ICON_NONE);

	game_setStatusText(timeout);

	return 0;
}

/** \brief Zeige ergebnis
 *	\param server_msg Beinhaltet Server Nachricht */
int show_result(struct RFC_MSG *server_msg) {

	uint8_t timeout;
	uint8_t correct;

	memcpy(&timeout, server_msg->data, sizeof(timeout));

	memcpy(&correct, server_msg->data + sizeof(timeout), sizeof(correct));

	if (timeout != 0) {

		game_setAnswerButtonsEnabled(0);

		game_markAnswerCorrect(correct);

		game_setStatusIcon(STATUS_ICON_TIMEOUT);

		game_setStatusText("Zeit abgelaufen...");

		return 0;
	}

	game_markAnswerCorrect(correct);

	int answer = get_answer();

	if (answer == correct) {

		game_setStatusIcon(STATUS_ICON_CORRECT);

		game_setStatusText("Antwort richtig!");

	} else {
		game_markAnswerWrong(answer);

		game_setStatusIcon(STATUS_ICON_WRONG);

		game_setStatusText("Antwort falsch!");
	}

	return 0;
}

/** \brief Wenn alle Fragen bearbeitet */
void wait_for_game_over() {

	game_setQuestion(
			"Du hast alle Fragen bearbeitet - warte auf andere Spieler");

	game_unmarkAnswers();

	game_setAnswerButtonsEnabled(0);

	int i;
	for (i = 0; i < 4; i++) {
		game_setAnswer(i, "");
	}

	game_setStatusIcon(STATUS_ICON_NONE);

	game_setStatusText("Warte auf Spielende...");

	return;
}

/** \brief Zeige Rang
 *	\param server_msg Beinhaltet Server Nachricht */
int show_rank(struct RFC_MSG *server_msg) {

	uint8_t rank = 0;

	memcpy(&rank, server_msg->data, sizeof(uint8_t));

	if (rank == 1) {

		infoPrint("Du bist Sieger!\n");

		guiShowMessageDialog("Du bist Sieger!", 1);

	} else {

		infoPrint("Du bist %d. !\n", rank);

		char rank_ch;

		sprintf(&rank_ch, "%d", rank);

		char rank_message[13] = { "Du bist ..!" };

		memcpy(rank_message + 8, &rank_ch, sizeof(char));

		guiShowMessageDialog(rank_message, 1);
	}

	return 0;
}

/** \brief Start des Listener-Thread
 *	\param param Standardübergabeparameter */
void* listener_thread(void* param) {

	sock = get_socket();

	client_id = get_client_id();

	// MSG-Struktur für Nachrichtenempfang
	struct RFC_MSG server_msg;

	uint8_t subtype = 0;

	char* err_msg;

	// While Schleife des Listener Thread
	// Wird verlassen bei schweren Fehlerfällen
	// und Ende des Spiels
	bool wait_for_dialog_exit = false;

	// Warte auf Command Thread bis dieser GUI initialisiert hat
	sem_wait(&command_init);

	// Zerstöre Semaphor, da nicht mehr benötigt
	sem_destroy(&command_init);

	/* ** ** ** While Schleife zum Empfangen von Nachrichten ** ** ** */

	while (1) {

		// Setze Message-Werte zurück
		server_msg.header.type = 0;
		server_msg.header.length = 0;

		/* ** ** ** Reagiere auf empfangenen MSG-Typ ** ** ** */

		switch (catch_com_err(receive_MSG(sock, &server_msg))) {

		// Wenn CATALOG_RESPONSE Nachricht empfangen wurde
		case CATALOG_RESPONSE:

			add_catalog(&server_msg);

			break;

			// Wenn CATALOG_CHANGE Nachricht empfangen wurde
		case CATALOG_CHANGE:

			highlight_catalog(&server_msg);

			break;

			// Wenn PLAYER_LIST Nachricht empfangen wurde
		case PLAYER_LIST:

			// Zeichne Spielerliste neu
			set_player_data(&server_msg);

			if (status == GAME_RUNNING) {

				game_player_list();

			} else {

				prep_player_list();
			}
			break;

			// Wenn START_GAME Nachricht empfangen wurde
		case START_GAME:

			start_game(&server_msg);

			set_game_status(GAME_RUNNING);

			sem_post(&question);

			break;

			// Wenn QUESTION Nachricht empfangen wurde
		case QUESTION:

			if (server_msg.header.length > 0) {

				new_question(&server_msg);

			} else {

				wait_for_game_over();
			}

			break;

			// Wenn QUESTION_RESULT Nachricht empfangen wurde
		case QUESTION_RESULT:

			show_result(&server_msg);

			// Setze Semaphor für Aktivierung von Fragewechsel-Thread
			sem_post(&question);

			break;

			// Wenn GAME_OVER Nachricht empfangen wurde
		case GAME_OVER:

			show_rank(&server_msg);

			wait_for_dialog_exit = true;

			break;

			/* ** ** ** ERROR wurde empfangen ** ** ** */

		case ERROR_WARNING:

			// Hole Speicher für Error-Msg
			err_msg = (char*) malloc(server_msg.header.length + sizeof(char));

			// err_msg mit Nullen füllen (automatische Ende-Markierung)
			memset(err_msg, 0, (server_msg.header.length + sizeof(char)));

			// Kopiere Subtyp und Fehlerbeschreibung aus Error-MSG
			memcpy(&subtype, server_msg.data, sizeof(uint8_t));
			memcpy(err_msg, server_msg.data + 1, server_msg.header.length
					- sizeof(uint8_t));

			// Unterscheide Error-Typ
			switch (subtype) {

			// Wenn Error-Typ Warnung
			case WARNING:

				// Ausgabe des Fehlers auf Konsole
				infoPrint("%s", err_msg);

				// Ausgabe des Fehlers in GUI
				guiShowMessageDialog(err_msg, 0);

				break;

				// Wenn Error-Typ Fatal
			case FATAL:

				// Ausgabe des Fehlers auf Konsole
				infoPrint("%s\n", err_msg);

				// Ausgabe des Fehlers in GUI
				guiShowErrorDialog(err_msg, 1);

				// Warte auf Schliessen des Dialogs
				wait_for_dialog_exit = true;

				break;

			default:

				infoPrint("Unbekannter Fehler ist im Server aufgetreten\n");

				guiShowErrorDialog("Unbekannter Fehler", 1);

				wait_for_dialog_exit = true;

				break;
			}

			break;

			/* ** ** ** RECEIVE ERRORS ** ** ** */

			// Wenn beim Empfangen ein Error auftritt
		case COM_ERR:

			infoPrint("Verbindung zu Server verloren\n");

			guiShowErrorDialog("Verbindung zu Server verloren", 1);

			wait_for_dialog_exit = true;

			break;

		default:
			break;
		}

		// Prüfe ob beim Empfangen Speicher allokiert wurde
		if ((server_msg.header.type != 0) && (server_msg.header.length != 0)) {

			// Freigeben des dynamischen Speichers (sockets. c - receive_MSG())
			free(server_msg.data);

			server_msg.data = NULL;
		}

		// Warteschleife von Listener, wenn Fataler Error oder Spielende
		while (wait_for_dialog_exit == true) {

			// Do nothing...
			// Warte auf Client, der das Error-Fenster schliesst
			// und damit den Command-Thread terminiert
			pthread_join(get_command_thread(), NULL);

			debugPrint("Ende des Listener-Thread");

			pthread_exit(NULL);
		}
	}

	debugPrint("Unerwartetes Ende Listener-Thread");

	pthread_exit(NULL);

	return NULL;
}
